#pragma once
#include<string>
#include<vector>
#include"oj_model.hpp"
#include"../comm/log.hpp"
#include"../comm/util.hpp"
#include"oj_view.hpp"
#include<assert.h>
#include<fstream>
#include"../comm/httplib.h"

namespace ns_control
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;
    //主机
    class Machine
    {
     public:
           std::string ip;
           int port;
           uint64_t load;
           std::mutex* mtx;
           public:
           Machine():ip(""),port(0),load(0),mtx(nullptr){}
           ~Machine(){}

    public:
             //负载++
            void IncLoad()
            {
                if(mtx) mtx->lock();
                load++;
                if(mtx)mtx->unlock();
            }
            //负载--
             void DecLoad()
            {
                if(mtx) mtx->lock();
                load++;
                if(mtx)mtx->unlock();
            }
            //获取负载
            uint64_t Load()
            {
                uint64_t  _load=0;
                if(mtx)mtx->lock();
                _load=load;
                if(mtx)mtx->unlock();
                return _load;
            }

    };
     std::string  service_machine="./conf/server_machine.conf";
    //负载均衡模块
    class LoadBlance
    {
    public:
           std::vector<Machine> machines;
            
           std::vector<int> online;

           std::vector<int> offline;
           std::mutex mtx;

     public:
     LoadBlance()
     {
    
      assert(LoadConf( service_machine));
      LOG(INFO)<<"加载配置文件成功！！！"<<"\n";

     }
     ~LoadBlance(){}
     public:
      bool LoadConf(const std::string & service_machine)
      {
         ifstream in(service_machine);
         if(!in.is_open())
         {
            LOG(ERROR)<<"打开配置文件失败！！！"<<"\n";
            return false;
         }
         std::string line;
         while(getline(in,line))
         {
             std::vector<std::string> tokens;
             StringUtil::SplitString(line,&tokens,":");
             if(tokens.size()!=2)
             {
                LOG(WARNING)<<"切分"<<":"<<line<<":失败"<<"\n";
                continue;
             }
             Machine m;
             m.ip=tokens[0];
             m.port=atoi(tokens[1].c_str());
             m.load=0;
             m.mtx=new mutex();
             online.push_back(machines.size());
             machines.push_back(m);
         }
         in.close();
         return true;
      }


      bool SmartChoice(int *id,Machine** m)
      {
        mtx.lock();
        int online_num=online.size();
        if(online_num==0)
        {
            mtx.unlock();
            LOG(FATAL)<<"所有后端编译主机已经离线，无法提供编译服务"<<"\n";
            return false;
        }
         *id=online[0];
         *m=&machines[online[0]];
        uint64_t min_load=machines[online[0]].Load();
        for(int i=1;i<online_num;i++)
        { 
             uint64_t curr_load=machines[online[i]].Load();
            // LOG(FATAL)<<"min_load:"<<min_load<<"curr_load:"<<curr_load<<"\n";
            if(min_load>curr_load)
            {
                min_load=curr_load;
                *id=online[i];
                *m=&machines[online[i]];
            }

        }
         mtx.unlock();
         return true;
      }

      void OfflineMachine(int id)
      {
          mtx.lock();
          for(auto iter=online.begin();iter!=online.end();iter++)
          {  
             if(*iter==id)
             {
                  online.erase(iter);
                  offline.push_back(id);
                  break;
             }

          }
          mtx.unlock();
      }
      void OnlineMachine(int id)
      {

      }
      void ShowMachines()
      {
        mtx.lock();
        std::cout<<"当前在线主机列表：";
        for(auto& id:online)
        {
            std::cout<<id<<" ";
        }
        std::cout<<"\n";
        std::cout<<"当前离线主机列表：";
        for(auto& id:offline)
        {
            std::cout<<id<<" ";
        }
        mtx.unlock();
      }

    };
 class Control
 {
    private:
     Models model_;
     View view_;
     LoadBlance load_blance;
    public:
    Control(){}
    ~Control(){}
    bool AllQuestions(std::string* html)
    { 
        bool ret=true;
        std::vector<struct Question> all;
        if(model_.GetAllQuestions(&all))
        {
          
            view_.AllExpandHtml(all, html);
        }else
        {

            *html = "获取题目失败, 形成题目列表失败";
                 ret = false;
        }
        return ret;
       
    }
    bool Question(const std::string number,std::string* html)
    {
        struct Question q;
        bool ret=true;
        if(model_.GetOneQuestions(number,&q))
        {
           
            view_.OneExpandHtml(q, html);
        }
        else
        {
        *html = "指定题目: " + number + " 不存在!";
         ret = false;

        }
        return ret;   
    }  
    void Judge(const string& number,const std::string in_json,std::string* out_json)
    {    //根据题目编号拿到对应的题目
     struct Question q;
     model_.GetOneQuestions(number,&q);
        //对in_json进行反序列化
        Json::Reader reader;
        Json::Value in_value;
        reader.parse(in_json,in_value);
        std::string code=in_value["code"].asString();
        //重新拼接用户代码+测试用例代码-----》新代码
          Json::Value compile_value;
          compile_value["input"]=in_value["input"].asString();
          compile_value["code"]=code+q.tail;

          std::cout<<code<<"\n";
          std::cout<<"-------------------";
          std::cout<<q.tail;
          compile_value["cpu_limit"]=q.cpu_limit;
          compile_value["mem_limit"]=q.mem_limit;
          //LOG(FATAL)<<"拼接用户代码成功"<<"\n";
          Json::FastWriter write;
          std::string compile_string=write.write(compile_value);
       
        
        while(true)
        {
            int id=0;
            Machine* m=nullptr;
            if(!load_blance.SmartChoice(&id,&m))
            {
                break;
            }
           
            //发起http请求，得到结果
            Client cli(m->ip,m->port);
            LOG(INFO)<<"选择主机成功，主机id:"<<id<<"详情："<<"ip:"<<m->ip<<"port:"<<m->port<<"\n";
            m->IncLoad();
            if(auto res=cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
            {
               
                if(res->status==200)
                {
                *out_json=res->body;
                m->DecLoad();
                LOG(INFO)<<"请求编译和运行服务成功！！！！"<<"\n";
                break;
               }
                 m->DecLoad();
            }
             else
            {
              LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";
              load_blance.OfflineMachine(id);
              load_blance.ShowMachines(); 
             }

        }

    }
 };



}

